package ws

import (
	"blockchain_data/handler/model"
	"blockchain_data/middleware"
	"blockchain_data/utils"
	"bytes"
	"compress/flate"
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"github.com/sirupsen/logrus"
	"io/ioutil"
	"net/http"
	"reflect"
	"sync"
	"time"
)


var log *logrus.Logger
func init()  {
	log = middleware.DataLogger()
}
type MethonInfo struct {
	Fun   interface{}
	Param reflect.Type
}

func (wsOp *MethonInfo) Init(fun interface{}, param reflect.Type) *MethonInfo {
	wsOp.Fun = fun
	wsOp.Param = param
	return wsOp
}

type OkWebSocketOp struct {
	Host string						//根路径
	Path string						//子路径
	Conn      *websocket.Conn

	Isclose  bool
	Reconnect bool


	SubStr []byte
	Fun    interface{}
	Param  reflect.Type
	Methon MethonInfo

	PingTime time.Duration
	Heart bool						//是否需要主动发送心跳
	StopHeart  chan int
	Ch chan model.Msg

	Mutex sync.Mutex
	IsFirst bool

	ClosePing chan bool
	ClosePong chan bool

}

func (wsOp *OkWebSocketOp) Open(path string, host string, heart bool,pingTime time.Duration) bool {
	defer wsOp.handler_error()

	wsOp.Host = host
	wsOp.Path = path
	wsOp.Heart = heart
	if(wsOp.Heart){
		wsOp.PingTime = pingTime
	}
	wsOp.Reconnect = false
	wsOp.IsFirst = true

	wsOp.ClosePing = make(chan bool)
	wsOp.ClosePong = make(chan bool)

	ret := wsOp.ConnServer()
	return ret
}

func (wsOp *OkWebSocketOp) Close() {
	defer wsOp.handler_error()

	wsOp.Isclose = true
	if wsOp.Conn != nil{
		wsOp.Conn.Close()
	}
	wsOp.Conn = nil
}

func (wsOp *OkWebSocketOp) ConnServer() bool {
	defer wsOp.handler_error()

	// 如果已经再重连，不能在调用
	if !wsOp.Reconnect {
		url := fmt.Sprintf("wss://%s%s", wsOp.Host, wsOp.Path)
		fmt.Println(url)
		var err error
		wsOp.Reconnect = true
		wsOp.Conn, _, err = (&websocket.Dialer{
			Proxy:            http.ProxyFromEnvironment,
			HandshakeTimeout: 45 * time.Second,
		}).Dial(url, nil)
		time.Sleep(4 * time.Second)
		if err != nil {
			log.Errorf("WebSocket 连接错误: %s", err,string(wsOp.SubStr))
			wsOp.Reconnect = false
			wsOp.ConnServer()
			return false
		}
		log.Info("WebSocket 连接成功")
		wsOp.Reconnect = false
		go wsOp.ReadLoop()

		if !wsOp.Isclose && len(wsOp.SubStr) != 0{
			wsOp.Sub(wsOp.SubStr,wsOp.Fun,wsOp.Param)
		}
		wsOp.Conn.SetCloseHandler(wsOp.OnClose)

		return true
	}else {
		return false
	}

}

func (wsOp *OkWebSocketOp) OnClose(code int, text string) error {
	defer wsOp.handler_error()

	wsOp.Conn = nil
	fmt.Println("OnClose重新连接",string(wsOp.SubStr))
	go wsOp.ConnServer()
	return fmt.Errorf("WebSocket 异常断开，尝试重新连接。。。")
}

func (wsOp *OkWebSocketOp) ReadLoop() {
	defer wsOp.handler_error()
	for  wsOp.Conn != nil{
		//log.Info("进入ReadLoop",string(wsOp.SubStr))
		messageType, message, err := wsOp.Conn.ReadMessage()
		if err != nil && !wsOp.Isclose{
			log.Error("ReadLoop",err)
			return
		}
		txtMsg := message
		switch messageType {
		case websocket.TextMessage:
		case websocket.BinaryMessage:
			txtMsg, err = wsOp.GzipDecode(message)
		}
		var jdata map[string]interface{}
		if string(txtMsg) == "pong" {
			wsOp.ClosePong <- true
		}else {
			err = json.Unmarshal([]byte(message), &jdata)
			if err != nil {
				log.Errorf("msg to map[string]json.RawMessage error: %s", err)
			}
			if _, found := jdata["data"]; found{
				wsOp.HandleSubCallbackFun(string(txtMsg))
			}else if wsOp.IsFirst{
				wsOp.IsFirst = false
				myMsg := &model.Msg{}
				err = utils.JsonBytes2Struct(txtMsg, myMsg)
				if err == nil {
					if myMsg.Event != "error"{
						wsOp.Ch <- model.Msg{Event: "success",Msg: "订阅成功"}
						//记录信息
						log.Info(string(message))
					}else {
						wsOp.Ch <- model.Msg{Event: "error",Msg: "订阅失败"}
						//记录信息
						log.Errorf(string(message))
					}
				}else {
					wsOp.Ch <- model.Msg{Event: "error",Msg: "订阅失败"}
					log.Errorf(string(message))
				}
			}
		}
	}
}

func (wsOp *OkWebSocketOp) HandleSubCallbackFun(data string) {
	defer wsOp.handler_error()
	var mi *MethonInfo = nil
	mi = new(MethonInfo).Init(wsOp.Fun, wsOp.Param)

	wsOp.RunFunction(mi, data)
}

func (wsOp *OkWebSocketOp) RunFunction(mi *MethonInfo, data string) {
	defer wsOp.handler_error()

	param := reflect.New(mi.Param).Interface()
	json.Unmarshal([]byte(data), &param)

	rargs := make([]reflect.Value, 1)

	rargs[0] = reflect.ValueOf(param)
	fun := reflect.ValueOf(mi.Fun)
	fun.Call(rargs)
}


func (wsOp *OkWebSocketOp) Sub(subStr []byte, fun interface{}, param reflect.Type) bool {
	defer wsOp.handler_error()

	wsOp.SubStr = subStr
	wsOp.Fun = fun
	wsOp.Param = param
	wsOp.Mutex.Lock()
	err := wsOp.Conn.WriteMessage(websocket.TextMessage, subStr)
	wsOp.Mutex.Unlock()
	if err != nil{
		log.Error(err)
	}else {
		log.Info("WebSocket 订阅成功",string(wsOp.SubStr))
		//订阅成功才能心跳
		if(wsOp.Heart){
			go wsOp.HeartWork()
		}
	}
	return true
}

func (wsOp *OkWebSocketOp) Unsub(unsubStr []byte) bool {
	defer wsOp.handler_error()

	wsOp.Mutex.Lock()
	wsOp.Conn.WriteMessage(websocket.TextMessage, unsubStr)
	wsOp.Mutex.Unlock()
	return true
}

func (wsOp *OkWebSocketOp) GzipDecode(in []byte) ([]byte, error) {
	defer wsOp.handler_error()

	reader := flate.NewReader(bytes.NewReader(in))
	defer reader.Close()

	return ioutil.ReadAll(reader)
}




// HeartWork 需要主动发心跳的work
func (wsOp *OkWebSocketOp)HeartWork()  {
	defer wsOp.handler_error()
	//每20秒发一次ping
	ticker := time.NewTicker(wsOp.PingTime)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			if wsOp.Conn != nil && !wsOp.Isclose {
				wsOp.Ping()
			}else {
				return
			}
		case <-wsOp.ClosePing:
			return
		}
	}
}



// Ping 如果有需要主动发送心跳重写
func (wsOp *OkWebSocketOp) Ping()  {
	defer wsOp.handler_error()
	msg := "ping"
	if wsOp.Conn != nil {
		wsOp.Mutex.Lock()
		wsOp.Conn.WriteMessage(websocket.TextMessage, []byte(msg))
		wsOp.Mutex.Unlock()
		go wsOp.Pong()
	}
}

// Pong 响应
func (wsOp *OkWebSocketOp) Pong()  {
	defer wsOp.handler_error()
	//如果20秒内没有pong就重新连接
	ticker := time.NewTicker(wsOp.PingTime - 2 * time.Second)
	defer ticker.Stop()
	select {
	case <-ticker.C:
		log.Error("pong主动重连",string(wsOp.SubStr))
		wsOp.Conn = nil
		wsOp.ClosePing <- true
		go wsOp.ConnServer()
		return
	case <-wsOp.ClosePong:
		return
	}
}

func (wsOp *OkWebSocketOp) handler_error() {
	if err := recover(); err != nil {
		log.Error(err)
		url := middleware.Sign()
		middleware.DingToInfo(err, url)
		time.Sleep(2*time.Second)
		wsOp.ConnServer()
	}
}